{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "2138aec8",
   "metadata": {},
   "source": [
    "# Response Types\n",
    "\n",
    "> A list of the different HTTP response types available to your FastHTML route handlers."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88eaa1a8",
   "metadata": {},
   "source": [
    "FastHTML provides multiple HTTP response types that automatically set the appropriate HTTP content type and handle serialization. The main response types are:\n",
    "\n",
    "- FT components\n",
    "- Redirects (HTTP 303 and other 3xx codes)\n",
    "- JSON (for API endpoints)\n",
    "- Streams (EventStream, for Server-Side Events)\n",
    "- Plaintext"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b6bbbec",
   "metadata": {},
   "source": [
    "::: {.callout-tip}\n",
    "## What about websockets?\n",
    "\n",
    "Websockets have their own protocol and don't follow the HTTP request/response cycle. To learn more, check out our explanation about websockets [here](/explains/websockets.html).\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b1f5040",
   "metadata": {},
   "source": [
    "## Configuration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "78cbdd09",
   "metadata": {},
   "outputs": [],
   "source": [
    "from fasthtml.common import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a42607c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "app,rt = fast_app()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c38a057e",
   "metadata": {},
   "source": [
    "`app` and `rt` are the common FastHTML route handler decorators. We instantiate them with the `fast_app` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "047ee3f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "cli = Client(app)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3d90266",
   "metadata": {},
   "source": [
    "FastHTML comes with the test client named `Client`. It allows us to test handlers via a simple interface where `.get()` is a `HTTP GET` request, `.post()` is a `HTTP POST` request."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b4499a6",
   "metadata": {},
   "source": [
    "## FT Component Response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f609069",
   "metadata": {},
   "outputs": [],
   "source": [
    "@rt('/ft')\n",
    "def get(): return Html(Div('FT Component Response'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e34a376",
   "metadata": {},
   "source": [
    "This is the response type you're probably most familiar with. Here the route handler returns an FT component, which FastHTML wraps in an HTML document with a head and body. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eb426503",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " <!doctype html>\n",
      " <html>\n",
      "   <div>FT Component Response</div>\n",
      " </html>\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(cli.get('/ft').text)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2508b86c",
   "metadata": {},
   "source": [
    "## Redirect Response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "94a0dca3",
   "metadata": {},
   "outputs": [],
   "source": [
    "@rt('/rr')\n",
    "def get(): return Redirect('https://fastht.ml/')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e63437b1",
   "metadata": {},
   "source": [
    "Here in this route handler, `Redirect` redirects the user's browser to the new URL 'https://fastht.ml/' "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f4a60242",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "http://testserver/rr\n",
      "303\n"
     ]
    }
   ],
   "source": [
    "resp = cli.get('/rr')\n",
    "print(resp.url)\n",
    "print(resp.status_code)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "264f5f02",
   "metadata": {},
   "source": [
    "You can see the URL in the response headers and `url` attribute, as well as a status code of 303. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "accd0739",
   "metadata": {},
   "source": [
    "## JSON Response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d5e4b375",
   "metadata": {},
   "outputs": [],
   "source": [
    "@rt('/json')\n",
    "def get(): return {'hello': 'world'}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fd9e7f7",
   "metadata": {},
   "source": [
    "This route handler returns a JSON response, where the `content-type` has been set to  . "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "de0bc762",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Headers({'content-length': '17', 'content-type': 'application/json'})\n",
      "{'hello': 'world'}\n"
     ]
    }
   ],
   "source": [
    "resp = cli.get('/json')\n",
    "print(resp.headers)\n",
    "print(resp.json())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43de3aff",
   "metadata": {},
   "source": [
    "You can see that the Content-Type header has been set to application/json, and that the response is simply the JSON without any HTML wrapping it."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e6a383a1",
   "metadata": {},
   "source": [
    "## EventStream"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "29807196",
   "metadata": {},
   "outputs": [],
   "source": [
    "from time import sleep\n",
    "\n",
    "def counter():\n",
    "    \"\"\"Counter is an generator that\n",
    "        publishes a number every second.\n",
    "    \"\"\"\n",
    "    for i in range(3):\n",
    "        yield sse_message(f\"Event {i}\")\n",
    "        sleep(1)\n",
    "\n",
    "@rt('/stream')\n",
    "def get():\n",
    "    return EventStream(counter())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ab837650",
   "metadata": {},
   "source": [
    "With server-sent events, it’s possible for a server to send new data to a web page at any time, by pushing messages to the web page. Unlike WebSockets, SSE can only go in one direction: server to client. SSE is also part of the HTTP specification unlike WebSockets which uses its own specification."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c13f277b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "event: message\n",
      "data: Event 0\n",
      "\n",
      "event: message\n",
      "data: Event 1\n",
      "\n",
      "event: message\n",
      "data: Event 2\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "resp = cli.get('/stream')\n",
    "print(resp.text)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe89e6bd",
   "metadata": {},
   "source": [
    "Each one of the message events above arrived one second after the previous message event."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c830a555",
   "metadata": {},
   "source": [
    "## Plaintext Response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7fa1ab1b",
   "metadata": {},
   "outputs": [],
   "source": [
    "@rt('/text')\n",
    "def get(): return 'Hello world'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fada95d6",
   "metadata": {},
   "source": [
    "When you return a string from a route handler, you get a plain-text response."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dc1cf7ba",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello world\n"
     ]
    }
   ],
   "source": [
    "print(cli.get('/text').text)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "335cfd15",
   "metadata": {},
   "source": [
    "Here you can see that the response text is simply the string you returned, without any HTML wrapping it."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
